﻿namespace Sai.Core
{
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.IO;
	using System.Linq;

	/// <summary>
	/// Load all specified assemblies and work out differences and commonality
	/// at binary "surface" level (no introspection).
	/// </summary>
	public class AssemblyComparisonBuilder
	{
		public AssemblyComparisonBuilder(ProductReleaseProject project)
		{
			this.Project = project;
		}

		public AssemblyComparisonBuilder(ProductReleaseProject project, FileSystemValidationOption validation)
		{
			this.Project = project;
			this.FileSystemValidation = validation;
		}

		public event EventHandler<CancelBuildEventArgs> BuildStarting;

        public event EventHandler<CancelBuildEventArgs> PreBuildStarting;

        public event EventHandler<CancelBuildEventArgs> PreBuildComplete;

		public event EventHandler<FolderEventArgs> LoadingFolder;

        public event EventHandler<CancelBuildEventArgs> LoadingComplete;

		public event EventHandler<BuildErrorEventArgs> BuildError;

        public event EventHandler<CancelBuildEventArgs> PostBuildStarting;

        public event EventHandler<CancelBuildEventArgs> PostBuildComplete;

        public event EventHandler BuildComplete;

		/// <summary>
		/// The project to run comparisons on.
		/// </summary>
		private ProductReleaseProject Project { get; set; }

		private FileSystemValidationOption FileSystemValidation { get; set; }

		public ReadOnlyCollection<AssemblyComparison> Build(IReportProgress progress)
		{
            try
            {
                this.OnBuildStarting();

                this.ExecutePreBuildSteps();

                var list = this.BuildInternal(progress);

                this.ExecutePostBuildSteps();

                this.OnBuildComplete();

                return list;
            }
            catch (BuildCancelledException)
            {
                throw;
            }
            catch (Exception ex)
            {
                this.OnError(ex);
            }

			return new ReadOnlyCollection<AssemblyComparison>(new List<AssemblyComparison>());
		}

		private void ExecutePreBuildSteps()
		{
			this.OnPreBuildStarting();
            this.CheckPreconditions();
            this.OnPreBuildComplete();
		}

		private void CheckPreconditions()
		{
			this.RequireFolderExists(this.Project.FromFolder);
			this.RequireFolderExists(this.Project.ToFolder);
		}

		private void RequireFolderExists(string folder)
		{
			if (this.FileSystemValidation == FileSystemValidationOption.EnsureExists
				&& !string.IsNullOrEmpty(folder) && !Directory.Exists(folder))
			{
				throw new DirectoryNotFoundException(folder);
			}
		}

		private ReadOnlyCollection<AssemblyComparison> BuildInternal(IReportProgress progress)
		{
			ProductRelease lastRelease = null;

			if (this.Project.FromAssemblies.Any())
			{
				lastRelease = this.CreateRelease(this.Project.FromVersion, this.Project.FromAssemblies);
			}
			else
			{
				this.OnLoadingFolder(this.Project.FromFolder);
				
				lastRelease = this.CreateRelease(this.Project.FromVersion, this.Project.FromFolder);

                this.OnLoadingComplete();
			}

			ProductRelease newRelease = null;

			if (this.Project.ToAssemblies.Any())
			{
				newRelease = this.CreateRelease(this.Project.ToVersion, this.Project.ToAssemblies); 
			}
			else
			{
				this.OnLoadingFolder(this.Project.ToFolder);

				newRelease = this.CreateRelease(this.Project.ToVersion, this.Project.ToFolder);

                this.OnLoadingComplete();
			}

			return newRelease.CompareTo(lastRelease);
		}

		private ProductRelease CreateRelease(string versionName, IEnumerable<string> fileNames)
		{
			return ProductReleaseBuilder.ProductRelease()
					.Named(versionName)
					.WithFiles(fileNames)
					.ValidateFiles(this.FileSystemValidation)
					.Build();
		}

		private ProductRelease CreateRelease(string versionName, string folderName)
		{
			return ProductReleaseBuilder.ProductRelease()
												.Named(versionName)
												.InFolder(folderName)
												.ValidateFiles(this.FileSystemValidation)
												.Build();
		}

		private void ExecutePostBuildSteps()
		{
			this.OnPostBuildStarting();

			/* nothing */

			this.OnPostBuildComplete();
		}

		private void OnBuildStarting()
		{
            CancelBuildEventArgs cancel = new CancelBuildEventArgs();
			this.BuildStarting.Fire(this, cancel);

            if (cancel.CancelAction)
                throw new BuildCancelledException();
		}

        private void OnPreBuildStarting()
		{
            CancelBuildEventArgs cancel = new CancelBuildEventArgs();
            this.PreBuildStarting.Fire(this, cancel);

            if (cancel.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnPreBuildComplete()
		{
            CancelBuildEventArgs cancel = new CancelBuildEventArgs();
            this.PreBuildComplete.Fire(this, cancel);

            if (cancel.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnLoadingFolder(string folder)
		{
            FolderEventArgs folderArgs = new FolderEventArgs(folder);
            this.LoadingFolder.Fire(this, folderArgs);

            if (folderArgs.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnLoadingComplete()
		{
            CancelBuildEventArgs cancel = new CancelBuildEventArgs();
            this.LoadingComplete.Fire(this, cancel);

            if (cancel.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnPostBuildStarting()
		{
            CancelBuildEventArgs cancel = new CancelBuildEventArgs();
            this.PostBuildStarting.Fire(this, cancel);

            if (cancel.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnPostBuildComplete()
		{
            CancelBuildEventArgs cancel = new CancelBuildEventArgs();
            this.PostBuildComplete.Fire(this, cancel);

            if (cancel.CancelAction)
                throw new BuildCancelledException();
        }

		private void OnError(Exception ex)
		{
			this.BuildError.Fire(this, new BuildErrorEventArgs(ex));
		}

		private void OnBuildComplete()
		{
			this.BuildComplete.Fire(this);
		}
	}
}
